#ifndef COMPONENTS_H
#define COMPONENTS_H

#include "Core/UUID.h"
#include "Renderer/texture.h"
#include "Scene/Mesh.h"
#include "Scene/Physics.h"
#include "Scene/Script.h"
#include "Renderer/camera.h"
#include "Renderer/font.h"
#include "Renderer/shader.h"
#include "Renderer/framebuffer.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>

#include <unordered_map>

namespace CC
{
    typedef enum {
        ComponentType_ID = 0,
        ComponentType_Tag,
        ComponentType_Transform,
        ComponentType_Light,
        ComponentType_Material2D,
        ComponentType_MeshBasicMaterial,
        ComponentType_LineBasicMaterial,
        ComponentType_MeshModelMaterial,
        ComponentType_MeshCubeMaterial,
        ComponentType_Custom2DMaterial,
        ComponentType_Custom3DMaterial,
        ComponentType_Model,
        ComponentType_Mesh,
        ComponentType_Animation,//bone anim
        ComponentType_RigidBody,
        ComponentType_Camera,
        ComponentType_Script,
        ComponentType_Font,
        ComponentType_Shader,
        ComponentType_MouseArea,
        ComponentType_MAX,
    }ComponentType;

    typedef enum {
        TagType_Default = 0,
        TagType_FPS,
        TagType_Arrow,
        TagType_ArrowAxisX,
        TagType_ArrowAxisY,
        TagType_ArrowAxisZ,
        TagType_DragMove,
        TagType_DragMoveStatic,//photo border
        TagType_DragMoveStaticPhoto1,//photo border
        TagType_DragMoveStaticPhoto2,//photo border
        TagType_DragMoveStaticPhoto3,//photo border
        TagType_DragMoveStaticVideo,
        TagType_DragMoveStaticCar,
        TagType_DragMoveDynamicTrash,
        TagType_DragMoveDynamicExit,
        TagType_DragMoveIcon,
        TagType_Ground,
        TagType_Enemy,
    }TagType;

    class Component
    {
        public:
        ComponentType componentType;

        Component() = default;
        Component(const Component &) = default;
        virtual ~Component() {}
    };

    class IDComponent: public Component
    {
        public:
        UUID ID;

        IDComponent(){
            componentType = ComponentType_ID;
        }
        IDComponent(const IDComponent &) = default;
        IDComponent(UUID id) :ID(id){
            componentType = ComponentType_ID;
        }
        virtual ~IDComponent() {}
    };

    class TagComponent: public Component
    {
        public:
        std::string Tag;
        bool Visible = true;
        bool VisibleEditor = true;
        std::string RenderPassTag = "opaque";        

        TagType Type = TagType_Default;
        TagType SubType = TagType_Default;

        TagComponent() {
            componentType = ComponentType_Tag;
        }
        TagComponent(const TagComponent &) = default;
        TagComponent(const std::string &tag)
            : Tag(tag) {
            componentType = ComponentType_Tag;
        }
        virtual ~TagComponent() {}
    };

    class TransformComponent: public Component
    {
        public:
        glm::vec3 Translation = {0.0f, 0.0f, 0.0f};
        glm::vec3 Rotation = {0.0f, 0.0f, 0.0f};
        glm::vec3 Scale = {1.0f, 1.0f, 1.0f};

        glm::mat4 CustomTransform = glm::mat4(1.0);

        TransformComponent() {
            componentType = ComponentType_Transform;
        }
        TransformComponent(const TransformComponent &) = default;
        TransformComponent(const glm::vec3 &translation)
            : Translation(translation) {
            componentType = ComponentType_Transform;
        }

        TransformComponent(const glm::vec3 &translation, const glm::vec3 &rotation, const glm::vec3 &scale)
            : Translation(translation), Rotation(rotation), Scale(scale) {
            componentType = ComponentType_Transform;
        }

        glm::mat4 GetTransform() const
        {
            if (CustomTransform != glm::mat4(1.0)) return CustomTransform;

            glm::qua<float> q = glm::qua<float>(Rotation);
            glm::mat4 rotation = glm::mat4_cast(q);

            return glm::translate(glm::mat4(1.0f), Translation) * rotation * glm::scale(glm::mat4(1.0f), Scale);
        }
        glm::mat4 GetTransform(glm::vec3 parentScale) const
        {
            if (CustomTransform != glm::mat4(1.0)) return CustomTransform;

            glm::qua<float> q = glm::qua<float>(Rotation);
            glm::mat4 rotation = glm::mat4_cast(q);

            return glm::translate(glm::mat4(1.0f), Translation * parentScale) * rotation * glm::scale(glm::mat4(1.0f), Scale);
        }
        glm::mat4 GetTransformNoScale() const
        {
            if (CustomTransform != glm::mat4(1.0)) return CustomTransform;

            glm::qua<float> q = glm::qua<float>(Rotation);
            glm::mat4 rotation = glm::mat4_cast(q);

            return glm::translate(glm::mat4(1.0f), Translation) * rotation;
        }
        glm::mat4 GetTransformNoScale(glm::vec3 parentScale) const
        {
            if (CustomTransform != glm::mat4(1.0)) return CustomTransform;

            glm::qua<float> q = glm::qua<float>(Rotation);
            glm::mat4 rotation = glm::mat4_cast(q);

            return glm::translate(glm::mat4(1.0f), Translation * parentScale) * rotation;
        }
        void CalcCustomTransformByBoneMat(glm::mat4 mat) {

            glm::qua<float> q = glm::qua<float>(Rotation);
            glm::mat4 rotation = glm::mat4_cast(q);

            glm::mat4 a = glm::translate(glm::mat4(1.0f), Translation) * rotation * glm::scale(glm::mat4(1.0f), Scale);

            CustomTransform = a * mat;
        }
        virtual ~TransformComponent() {}
    };

    class LightComponent: public Component
    {
        public:
        glm::vec4 Color{ 1.0f, 1.0f, 1.0f, 1.0f };
        glm::vec4 Position{ 0.0f, 0.0f, 0.0f, 0.0f };//w=0,direct light;w=1,point light
        float Intensity;

        LightComponent() {
            componentType = ComponentType_Light;
        }
        LightComponent(const LightComponent &) = default;
        LightComponent(const glm::vec4 &color)
            : Color(color), Intensity(1.0) {
            componentType = ComponentType_Light;
        }
        virtual ~LightComponent() {}
    };

    // Material =========================
    class Material2DComponent: public Component
    {
        public:
        glm::vec4 Color{1.0f, 1.0f, 1.0f, 1.0f};
        Texture2D *Texture = nullptr;
        float TilingFactor = 1.0f;

        Material2DComponent() {
            componentType = ComponentType_Material2D;
        }
        Material2DComponent(const Material2DComponent &) = default;
        Material2DComponent(const glm::vec4 &color)
            : Color(color) {
            componentType = ComponentType_Material2D;
        }
        virtual ~Material2DComponent() {}
    };

    class MeshBasicMaterialComponent: public Component
    {
        public:
        glm::vec4 Color{1.0f, 1.0f, 1.0f, 1.0f};
        Texture2D *Texture = nullptr;
        float TilingFactor = 1.0f;

        MeshBasicMaterialComponent() {
            componentType = ComponentType_MeshBasicMaterial;
        }
        MeshBasicMaterialComponent(const MeshBasicMaterialComponent &) = default;
        MeshBasicMaterialComponent(const glm::vec4 &color)
            : Color(color) {
            componentType = ComponentType_MeshBasicMaterial;
        }
        virtual ~MeshBasicMaterialComponent() {}
    };

    class LineBasicMaterialComponent : public Component
    {
    public:
        glm::vec4 Color{ 1.0f, 1.0f, 1.0f, 1.0f };
        glm::vec3 StartPoint;
        glm::vec3 EndPoint;

        LineBasicMaterialComponent() {
            componentType = ComponentType_LineBasicMaterial;
        }
        LineBasicMaterialComponent(const LineBasicMaterialComponent &) = default;
        LineBasicMaterialComponent(const glm::vec4 &color)
            : Color(color), StartPoint(glm::vec3(0.0)), EndPoint(glm::vec3(0.0)) {
            componentType = ComponentType_LineBasicMaterial;
        }
        virtual ~LineBasicMaterialComponent() {}
    };

    class MeshModelMaterialComponent: public Component
    {
        public:
        bool HasColorMaterial = true;
        bool EanbleLight = true;
        bool EanbleGenerateShadow = false;
        bool EanbleDisplayShadow = false;
        MaterialMesh MaterialMeshType;
        std::vector<Texture2D *> Textures;
        float TilingFactor = 1.0f;

        MeshModelMaterialComponent() {
            componentType = ComponentType_MeshModelMaterial;
        }
        MeshModelMaterialComponent(const MeshModelMaterialComponent &) = default;
        MeshModelMaterialComponent(bool has, MaterialMesh m, std::vector<Texture2D *> t, float tf = 1.0f)
            : HasColorMaterial(has), MaterialMeshType(m), Textures(t), TilingFactor(tf) {
            componentType = ComponentType_MeshModelMaterial;
        }

        MeshModelMaterialComponent(bool has, MaterialMesh m, float tf = 1.0f)
            : HasColorMaterial(has), MaterialMeshType(m), TilingFactor(tf) {
            componentType = ComponentType_MeshModelMaterial;
        }
        virtual ~MeshModelMaterialComponent() {}
    };

    class MeshCubeMaterialComponent : public Component
    {
    public:
        glm::vec4 Color{ 1.0f, 1.0f, 1.0f, 1.0f };
        TextureCube *Texture = nullptr;
        float TilingFactor = 1.0f;

        MeshCubeMaterialComponent() {
            componentType = ComponentType_MeshCubeMaterial;
        }
        MeshCubeMaterialComponent(const MeshCubeMaterialComponent &) = default;
        MeshCubeMaterialComponent(TextureCube* t, const glm::vec4 &color)
            : Texture(t), Color(color) {
            componentType = ComponentType_MeshCubeMaterial;
        }
        virtual ~MeshCubeMaterialComponent() {}
    };

    class Custom2DMaterialComponent : public Component
    {
    public:
        enum DataType
        {
            DataType_Int = 0,
            DataType_Int2,
            DataType_Int3,
            DataType_Int4,
            DataType_Float,
            DataType_Float2,
            DataType_Float3,
            DataType_Float4,
            DataType_Texture,
        };
        struct DataInfo
        {
            DataType dataType;
            int a4[4];
            float b4[4];
            Texture2D* texture2D;

            DataInfo() {}

            DataInfo(DataType t, int v1, int v2 = 0, int v3 = 0, int v4 = 0) : dataType(t){
                a4[0] = v1;
                a4[1] = v2;            
                a4[2] = v3;            
                a4[3] = v4;            
            }

            DataInfo(DataType t, float v1, float v2 = 0.0, float v3 = 0.0, float v4 = 0.0) : dataType(t) {
                b4[0] = v1;
                b4[1] = v2;
                b4[2] = v3;
                b4[3] = v4;
            }

            DataInfo(DataType t, Texture2D* tex) : dataType(t), texture2D(tex) {
            }
        };

        std::unordered_map<std::string, DataInfo> datas;

        Custom2DMaterialComponent() {
            componentType = ComponentType_Custom2DMaterial;
        }
        Custom2DMaterialComponent(const Custom2DMaterialComponent &) = default;
        virtual ~Custom2DMaterialComponent() {}
    };

    class Custom3DMaterialComponent : public Component
    {
    public:
        enum DataType
        {
            DataType_Int = 0,
            DataType_Int2,
            DataType_Int3,
            DataType_Int4,
            DataType_Float,
            DataType_Float2,
            DataType_Float3,
            DataType_Float4,
            DataType_Texture,
        };
        struct DataInfo
        {
            DataType dataType;
            int a4[4];
            float b4[4];
            Texture2D* texture2D;

            DataInfo() {}

            DataInfo(DataType t, int v1, int v2 = 0, int v3 = 0, int v4 = 0) : dataType(t) {
                a4[0] = v1;
                a4[1] = v2;
                a4[2] = v3;
                a4[3] = v4;
            }

            DataInfo(DataType t, float v1, float v2 = 0.0, float v3 = 0.0, float v4 = 0.0) : dataType(t) {
                b4[0] = v1;
                b4[1] = v2;
                b4[2] = v3;
                b4[3] = v4;
            }

            DataInfo(DataType t, Texture2D* tex) : dataType(t), texture2D(tex) {
            }
        };

        std::unordered_map<std::string, DataInfo> datas;

        Custom3DMaterialComponent() {
            componentType = ComponentType_Custom3DMaterial;
        }
        Custom3DMaterialComponent(const Custom3DMaterialComponent &) = default;
        virtual ~Custom3DMaterialComponent() {}
    };
    // Material =========================

    class ModelComponent: public Component
    {
        public:
        std::string Path;        

        ModelComponent() {
            componentType = ComponentType_Model;
        }
        ModelComponent(const ModelComponent &) = default;
        ModelComponent(const std::string p)
            : Path(p) {
            componentType = ComponentType_Model;
        }
        virtual ~ModelComponent() {}
    };

    class MeshComponent: public Component
    {
        public:
        Mesh MeshData;
        Mesh* MeshDataInstance = nullptr;

        MeshComponent() {
            componentType = ComponentType_Mesh;
        }
        MeshComponent(const MeshComponent &) = default;
        MeshComponent(const Mesh m)
            : MeshData(m) {
            componentType = ComponentType_Mesh;
        }
        virtual ~MeshComponent() {}
    };

    class AnimationComponent: public Component
    {
        public:
        // Mesh MeshData;
        Animator *AnimatorObject=NULL;
        AnimationInfo AnimationInfoData;
        std::string CurrentAnimatioName = "";
        std::string LastAnimatioName = "";
        bool AnimationLoop = true;
        bool AnimationFinish = false;
        double AnimationMixTime = 0.0;
        double AnimationSpeed = 1.0;
        int AnimationCurrentFrame = 0;
        bool AnimationPlaying = false;

        AnimationComponent() {
            componentType = ComponentType_Animation;
        }
        AnimationComponent(const AnimationComponent &a) = default;

        AnimationComponent(AnimationInfo& a) :AnimationInfoData(a)
        {
            CC_CORE_INFO("~~~*************~AnimationComponent(AnimationInfo& a)\n");
            if (AnimatorObject) {
                delete AnimatorObject;
                AnimatorObject = NULL;
            }
            AnimatorObject = new Animator;
            componentType = ComponentType_Animation;
        }

        virtual ~AnimationComponent() {
            CC_CORE_INFO("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~AnimationComponent\n");
            //delete AnimatorObject;
            //AnimatorObject = NULL;
        }

        void AddAnimation(std::string name, std::string animationPath)
        {
            if (AnimationInfoData.BoneCounter == 0) {
                CC_CORE_INFO("BoneCounter=0\n");
                return;
            }

            if (AnimationInfoData.AnimationInfoMap.find(name) != AnimationInfoData.AnimationInfoMap.end()) {
                CC_CORE_INFO("Anim has add\n");
                return;
            }

             Animation *animation = new Animation(animationPath, AnimationInfoData);
             AnimationInfoData.AnimationInfoMap[name] = animation;// Animation(animationPath, AnimationInfoData);
        }

        void DelAnimation(std::string name)
        {
            auto it = AnimationInfoData.AnimationInfoMap.find(name);
            if(it == AnimationInfoData.AnimationInfoMap.end())
                return;

            if (name == CurrentAnimatioName) {
                CurrentAnimatioName = "";
                StopAnimation();
            }

            delete it->second;
            AnimationInfoData.AnimationInfoMap.erase(it);
        }

        bool PlayAnimation(std::string name, bool repeate=true){
            if (AnimationInfoData.AnimationInfoMap.find(name) == AnimationInfoData.AnimationInfoMap.end()) {
                CurrentAnimatioName = "";
                return false;
            }
            //if(CurrentAnimatioName == name) return false;
            if (CurrentAnimatioName != name) {
                AnimationPlaying = false;
            }
            if (AnimationPlaying) return false;
            AnimationPlaying = true;

            AnimationLoop = repeate;

            if(!repeate){
                LastAnimatioName = CurrentAnimatioName;
                AnimationFinish = false;
            }

            CurrentAnimatioName = name;

            if(AnimatorObject)
                AnimatorObject->PlayAnimation(AnimationInfoData.AnimationInfoMap[name], AnimationMixTime);
            return true;
        }
        void StopAnimation(){
            if (AnimatorObject)
                AnimatorObject->StopAnimation();
            AnimationPlaying = false;
        }
        void Clear() {
            CurrentAnimatioName = LastAnimatioName = "";

            AnimationLoop = true;
            AnimationFinish = false;
            AnimationMixTime = 0.0;
            AnimationSpeed = 1.0;
            AnimationPlaying = false;
        }
    };

    class RigidBodyComponent: public Component
    {
        public:
        RigidBody RigidBodyData;

        RigidBodyComponent() {
            componentType = ComponentType_RigidBody;
        }
        RigidBodyComponent(const RigidBodyComponent &) = default;
        RigidBodyComponent(const RigidBody r)
            : RigidBodyData(r) {
            componentType = ComponentType_RigidBody;
        }
        virtual ~RigidBodyComponent() {}
    };

    class CameraComponent: public Component
    {
        public:
        Camera *camera;
        bool Primary = true; // TODO: think about moving to Scene
        bool Type2d = true;  // camera 2d
        bool FixedAspectRatio = false;

        CameraComponent() {
            componentType = ComponentType_Camera;
        }
        CameraComponent(const CameraComponent &) = default;
        CameraComponent(Camera* c, bool p, bool t, bool f) : camera(c), Primary(p), Type2d(t), FixedAspectRatio(f) {
            componentType = ComponentType_Camera;
        }
        virtual ~CameraComponent() {}
    };

    class ScriptComponent: public Component
    {
        public:
        std::string ClassName;
        std::string Path;
        Script *ScriptObject = nullptr;

        ScriptComponent() {
            componentType = ComponentType_Script;
        }
        ScriptComponent(const ScriptComponent &) = default;
        ScriptComponent(const std::string c="", const std::string p=""): ClassName(c), Path(p) {
            componentType = ComponentType_Script;
        }
        virtual ~ScriptComponent() {}
    };

    class FontComponent: public Component
    {
        public:
        Font* font;
        float scale = 1.0f; // output 100 * 0.16 = 16px
        std::string text = ""; //del
        std::wstring wtext;
        unsigned int fontSize = 16;

        FontComponent() {
            componentType = ComponentType_Font;
        }
        FontComponent(const FontComponent &) = default;
        FontComponent(Font* f, const float &s = 1.0, const unsigned int &fs = 40)
            : font(f), scale(s), fontSize(fs) {
            componentType = ComponentType_Font;
        }
        virtual ~FontComponent() {}
    };

    class ShaderComponent : public Component
    {
    public:
        Shader* shader;

        ShaderComponent() {
            componentType = ComponentType_Shader;
        }
        ShaderComponent(const ShaderComponent &) = default;
        ShaderComponent(Shader* s)
            : shader(s){
            componentType = ComponentType_Shader;
        }
        virtual ~ShaderComponent() {}
    };

    class MouseAreaComponent : public Component
    {
    public:
        MouseAreaComponent() {
            componentType = ComponentType_MouseArea;
        }
        MouseAreaComponent(const MouseAreaComponent &) = default;

        virtual ~MouseAreaComponent() {}

        typedef std::function<void(Event*, Entity*)> MouseAreaEventCallbackFn;

        template <typename T>
        void AddHandler(const MouseAreaEventCallbackFn &func) {
            m_funcs[T::GetStaticType()] = func;
        }

        void OnEvent(Event &event, Entity* e)
        {
            std::unordered_map<EventType, MouseAreaEventCallbackFn>::iterator it = m_funcs.begin();
            for (it; it != m_funcs.end(); ++it) {
                if (event.GetEventType() == it->first)
                {
                    it->second(&event, e);
                    break;
                }
            }
        }

        std::unordered_map<EventType, MouseAreaEventCallbackFn> m_funcs;
    };
}

#endif